contents
자바에서 Iterator 는 컬렉션의 내부 구조(예: ArrayList, LinkedList, HashSet인지 여부)를 노출하지 않고, 컬렉션의 원소를 하나씩 순회하는 표준적이고 보편적인 방법을 제공하는 인터페이스입니다.
Iterator가 해결하는 문제
리스트의 경우 인덱스를 사용하는 일반 for 루프를 사용할 수 있지만, 인덱스가 없는 세트(Set)나 다른 유형의 컬렉션에서는 작동하지 않습니다. Iterator는 두 가지 주요 문제를 해결합니다.
- 보편적인 순회:
Iterable인터페이스를 구현하는 모든 컬렉션을 순회할 수 있는 단일하고 일관된 방법(.iterator())을 제공합니다. 이는 내부 세부 사항을 추상화하므로,ArrayList를 순회하는 코드와HashSet을 순회하는 코드가 완전히 동일할 수 있습니다. - 반복 중 안전한 제거: 이것이
Iterator를 직접 사용하는 가장 중요한 이유입니다. 일반적인 for-each 루프를 사용하여 컬렉션을 순회하는 동안 항목을 제거하려고 하면ConcurrentModificationException이 발생합니다.Iterator는 컬렉션을 순회하면서 수정할 수 있는 유일하고 안전한 방법을 제공합니다.
Iterator 인터페이스: 핵심 메서드 🚶♂️
Iterator 인터페이스는 매우 단순하며 세 가지 핵심 메서드를 가지고 있습니다.
boolean hasNext(): 방문할 원소가 컬렉션에 더 있는지 확인합니다. 루프를 제어하는 데 사용됩니다.E next(): 시퀀스의 다음 원소를 반환하고 이터레이터의 커서를 다음 위치로 이동시킵니다.void remove():next()메서드에 의해 가장 최근에 반환된 원소를 내부 컬렉션에서 제거합니다.
자바 8에서는 남은 모든 원소에 대해 주어진 작업을 수행하는 forEachRemaining(action)이라는 디폴트 메서드가 추가되었습니다.
Iterator 사용 방법
모든 컬렉션 객체에서 .iterator() 메서드를 호출하여 Iterator 인스턴스를 얻을 수 있습니다. 표준적인 사용법은 while 루프와 함께 사용하는 것입니다.
기본 순회
List fruits = new ArrayList<>(List.of("Apple", "Banana", "Cherry"));
Iterator iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
}
올바른 방법: 안전한 제거 🗑️
반복 중에 원소를 안전하게 제거하는 방법은 다음과 같습니다. 핵심은 iterator.remove() 메서드를 사용하는 것입니다.
List fruits = new ArrayList<>(List.of("Apple", "Banana", "Cherry"));
Iterator iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
if (fruit.equals("Banana")) {
// 이것이 반복 중에 제거하는 유일하고 안전한 방법입니다.
iterator.remove();
}
}
System.out.println(fruits); // 출력: [Apple, Cherry]
중요: remove()는 next() 호출 한 번당 한 번만 호출할 수 있습니다. next()를 적어도 한 번 호출하기 전에는 remove()를 호출할 수 없습니다.
잘못된 방법: 안전하지 않은 제거
for-each 루프 내에서 컬렉션 자체의 remove() 메서드를 사용하려고 하면 코드가 충돌합니다.
List fruits = new ArrayList<>(List.of("Apple", "Banana", "Cherry"));
try {
for (String fruit : fruits) {
if (fruit.equals("Banana")) {
// 잘못된 방법! ConcurrentModificationException이 발생합니다.
fruits.remove(fruit);
}
}
} catch (ConcurrentModificationException e) {
System.out.println("오류: " + e.getMessage());
}
For-Each 루프: 문법적 설탕 (Syntactic Sugar) 🍬
"for-each 루프가 제거에 그렇게 위험하다면 왜 존재하는 걸까?"라고 궁금해할 수 있습니다. 그 답은 for-each 루프(향상된 for문이라고도 함)가 사실은 Iterator를 위한 문법적 설탕에 불과하기 때문입니다.
다음과 같이 코드를 작성하면:
for (String fruit : fruits) {
System.out.println(fruit);
}
자바 컴파일러는 실제로 내부적으로 이것을 다음과 같이 변환합니다:
Iterator iterator = fruits.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
}
이것이 ConcurrentModificationException이 발생하는 이유입니다. for-each 루프는 순회를 관리하기 위해 비밀리에 이터레이터를 사용하고 있습니다. 이때 fruits.remove()를 호출하면, 이터레이터 모르게 리스트를 직접 수정하는 셈이 됩니다. 이터레이터는 다음 작업에서 이 외부 수정을 감지하고 예측할 수 없는 동작을 방지하기 위해 예외를 던집니다.
관련 인터페이스
Iterable<T>:iterator()라는 단일 메서드를 가진 인터페이스로,Iterator를 반환합니다.Iterable을 구현하는 모든 클래스는 for-each 루프에서 사용할 수 있습니다. 모든 표준 자바 컬렉션은Iterable을 구현합니다.ListIterator<E>:Iterator의 더 강력한 하위 인터페이스로, 특히List를 위한 것입니다. 추가적인 기능을 제공합니다.- 양방향 순회:
hasPrevious()와previous()로 뒤로 갈 수 있습니다. - 원소 수정:
set(E e)으로 마지막으로 반환된 원소를 교체할 수 있습니다. - 원소 추가:
add(E e)으로 현재 위치에 원소를 추가할 수 있습니다.
- 양방향 순회:
references